package com.distributed.lock.controller;

import com.distributed.lock.utils.RedissonUtils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @program: SpringBootDemos
 * @description: 集群或者微服务或者分布式下(总之不是一个单实例的环境)应用下数据一致性问题
 * https://blog.csdn.net/zxd1435513775/article/details/122194202
 * @author: Kangsen
 * @create: 2022-07-12 15:51
 **/
@RestController
@Slf4j
public class MultipleController {

    /**
     * 采用redis的分布式锁
     */
    private static final String REDIS_LOCK = "goods_lock";
    @Resource
    private RedisTemplate redisTemplate;


    /**
     * 加分布式锁
     * 用户购买某商品
     *
     * V1 解决了分布式中 数据唯一的问题
     * 但是还有问题 这个服务在释放锁前 宕机了  锁没有被释放掉  会造成资源被死锁
     *
     * @return
     */
    @GetMapping("/mbuyV1")
    public synchronized String buyGoodsV1() {
        try {
            String uuid = UUID.randomUUID().toString();
            //key不存在时才会设置kv
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, uuid);
            if(!aBoolean){
                return "加锁失败";
            }
            int stock = (int) redisTemplate.opsForValue().get("goods:1");
            if(stock > 0){
                log.info("库存:{}",stock);
                redisTemplate.opsForValue().set("goods:1",stock - 1);
                return "库存:"+stock;
            }else{
                log.info("无库存");
                return "无库存";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "异常:"+e.getMessage();
        } finally {
            redisTemplate.delete(REDIS_LOCK);
        }
    }

    /**
     * 加分布式锁
     * 用户购买某商品
     *
     * V1 解决了分布式中 数据唯一的问题
     * 但是还有问题 这个服务在释放锁前 宕机了  锁没有被释放掉  会造成资源被死锁
     * 解决方法：redis加key的过期时间
     * 同时会引入新的问题: 业务在锁持有的这段时间如果不能执行完就会出问题
     *
     * @return
     */
    @GetMapping("/mbuyV2")
    public synchronized String buyGoodsV2() {
        try {
            String uuid = UUID.randomUUID().toString();
            //key不存在时才会设置kv  设置过期时间10s
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, uuid , 10 , TimeUnit.SECONDS);
            if(!aBoolean){
                return "加锁失败";
            }
            int stock = (int) redisTemplate.opsForValue().get("goods:1");
            if(stock > 0){
                log.info("库存:{}",stock);
                redisTemplate.opsForValue().set("goods:1",stock - 1);
                return "库存:"+stock;
            }else{
                log.info("无库存");
                return "无库存";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "异常:"+e.getMessage();
        } finally {
            redisTemplate.delete(REDIS_LOCK);
        }
    }

    /***
     * V2 中若持有锁的这段时间(key设置了过期时间) 后续业务耗时较多的业务执行完后 回来删除锁  有可能会把别的线程的锁删掉
     * 所有解决办法就是谁加的锁 谁来删除
     * 解决办法：谁加的锁谁才能删除
     *
     * 但这种方案实际也有问题 finally{}块并不是原子操作  也就是说 判断和 删除不是原子的 会造成删除别的线程的锁
     * 比如 判断完成过后 正要删除锁的时候 这时候CPU去执行别的任务  下次切换后 回来的时候就 会直接删除锁
     * 但此时极有可能 锁超时被自动删除 别的线程已经加锁了  这种情况下删除锁 会把别的线程的锁删掉
     *
     * mbuyV3
     * @return
     */
    @GetMapping("/mbuyV3")
    public synchronized String buyGoodsV3() {
        String uuid = UUID.randomUUID().toString();
        try {
            //key不存在时才会设置kv  设置过期时间10s
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, uuid , 10 , TimeUnit.SECONDS);
            if(!aBoolean){
                return "加锁失败";
            }
            int stock = (int) redisTemplate.opsForValue().get("goods:1");
            if(stock > 0){
                log.info("库存:{}",stock);
                redisTemplate.opsForValue().set("goods:1",stock - 1);
                return "库存:"+stock;
            }else{
                log.info("无库存");
                return "无库存";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "异常:"+e.getMessage();
        } finally {
            //必须是自己
            String value = String.valueOf(redisTemplate.opsForValue().get(REDIS_LOCK));
            if (uuid.equalsIgnoreCase(value)) {
                redisTemplate.delete(REDIS_LOCK);
            }
        }
    }

    private DefaultRedisScript<List> getRedisScript;

    public MultipleController(){
        log.info("====>MultipleController init<====");
        String script =
                "if redis.call('get',KEYS[1]) == ARGV[1] " +
                "then " +
                "   return redis.call('del'，KEYS[1])" +
                "else" +
                "   return 0" +
                "end";
        ByteArrayResource resource = new ByteArrayResource(script.getBytes());
        getRedisScript = new DefaultRedisScript<>();
        getRedisScript.setResultType(List.class);
        getRedisScript.setScriptSource(new ResourceScriptSource(resource));
    }

    /***
     * 解决V3的问题 还得是lua脚本
     *
     *
     * 但是还是会有问题：
     *       1、业务超时执行缓存没有续命
     *       2、redis 集群环境下 主从复制数据同步问题  主节点没有把锁数据同步到其他节点就宕机了 造成锁失效
     *     解决方式： 采用redisson 实现的RedLock
     * @return
     */
    @GetMapping("/mbuyV4")
    public synchronized String buyGoodsV4() {
        String uuid = UUID.randomUUID().toString();
        try {
            //key不存在时才会设置kv  设置过期时间10s
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, uuid , 10 , TimeUnit.SECONDS);
            if(!aBoolean){
                return "加锁失败";
            }
            int stock = (int) redisTemplate.opsForValue().get("goods:1");
            if(stock > 0){
                log.info("库存:{}",stock);
                redisTemplate.opsForValue().set("goods:1",stock - 1);
                return "库存:"+stock;
            }else{
                log.info("无库存");
                return "无库存";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "异常:"+e.getMessage();
        } finally {
            //必须是自己上得锁 自己来解锁  lua 脚本来执行判断 删除逻辑  原子性的
            Object execute = redisTemplate.execute(getRedisScript, Collections.singletonList(REDIS_LOCK), uuid);
            log.info("释放锁：{}",execute);
            if ("1".equalsIgnoreCase(execute.toString())) {
                log.info("删除成功");
            }
        }
    }

    /***
     * 采用redission
     * 解决
     * 1、 续命问题  分布式锁任务超时执行时 锁续期的问题
     * 2、集群环境下 主从节点若还未同步 主节点挂掉 导致锁出现的问题
     *
     * 没有设置锁的超时时间  看门狗会对线程进行续命
     *
     * @return
     */
    @Resource
    private RedissonUtils redissonUtils;
    @GetMapping("/mbuyV5")
    public synchronized String buyGoodsV5() {
        RLock lock = redissonUtils.lock(REDIS_LOCK);
        String uuid = UUID.randomUUID().toString();
        try {
            int stock = (int) redisTemplate.opsForValue().get("goods:1");
            if(stock > 0){
                log.info("库存:{}",stock);
                redisTemplate.opsForValue().set("goods:1",stock - 1);
                return "库存:"+stock;
            }else{
                log.info("无库存");
                return "无库存";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "异常:"+e.getMessage();
        } finally {
            //锁被持有中并且是当前线程加的锁 就释放锁
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                redissonUtils.unlock(lock);
            }
        }
    }
    public static final DateTimeFormatter F_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
            .withZone(ZoneId.of("Asia/Shanghai")); //GMT+8
    public static final DateTimeFormatter DB_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            .withZone(ZoneId.of("Asia/Shanghai")); //GMT+8


    public static void main(String[] args) {
        /*ConcurrentLinkedDeque<String> concurrentLinkedDeque = new ConcurrentLinkedDeque();
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            concurrentLinkedDeque.add(i +"");
        }
        log.info("concurrentLinkedDeque:{}",concurrentLinkedDeque);
        log.info("concurrentLinkedDeque:{}",concurrentLinkedDeque.poll());
        System.out.println(list);*/

        String matchDate = "2022-08-21";
        System.out.println(LocalDateTime.now().plusDays(3L).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")).compareTo(matchDate) >= 0
                &&
                LocalDateTime.now().minusDays(1L).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")).compareTo(matchDate) <= 0);



    }
    private static ConcurrentHashMap<String, Object> localMap = new ConcurrentHashMap<>();
    public static void removeAnalyzeData(String matchId) {

        synchronized (localMap) {
            Set<String> analyzeMatch = (Set<String>) localMap.get("analyze_match");
            if (analyzeMatch == null) {
                return;
            } else {
                if(analyzeMatch.contains(matchId)){
                    analyzeMatch.remove(matchId);
                }
            }
        }
    }
}
